package com.nx.platform.entry.filter;


import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import com.google.common.collect.Sets;
import com.nx.platform.entry.annotation.scan.AnnotationScan;
import com.nx.platform.entry.service.CommondFunction;
import com.nx.platform.entry.utility.Constant;
import com.nx.platform.entry.utility.E2s;
import com.nx.platform.entry.utility.RequestUtils;
import com.nx.platform.entry.utility.ResponseUtils;
import com.nx.platform.openentry.annotation.RPCMethod;

import lombok.extern.slf4j.Slf4j;

/**
 * filterName = "filter1" 用来置顶 filter 的执行顺序
 */
@WebFilter(urlPatterns = {"/*"},
        dispatcherTypes = {DispatcherType.REQUEST},
        filterName = "filter3"
)
@Slf4j
@Component
public class PpuCheckFilter implements Filter {

    @Autowired
    CommondFunction commondFunction;

    @Autowired
    AnnotationScan scan;

    private static final char DEFAULT_POINT = '.';



    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) request;
        HttpServletResponse httpRes = (HttpServletResponse) response;

        // 对于register、login等非登录要求的接口直接放过
        if (httpReq.getRequestURI().contains("transfer") || scan.getNotCheckUri().contains(httpReq.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }

        //获取Seesion
        Cookie[] cookies = httpReq.getCookies();
        String session = "";
        Long uid = null;
        if (cookies != null && cookies.length > 0) {
            log.info(" act=PpuCheckFilter.doFilter ", " cookies 为空 >>>", " <<<<");
            for (Cookie cookie : cookies) {
                if ("uid".equals(cookie.getName())) {
                    uid = Long.valueOf(cookie.getValue());
                }
                if ("session".equals(cookie.getName())) {
                    session = cookie.getValue();
                }
            }
        }

        boolean checkResult = checkPpu(session, uid, httpReq, (HttpServletResponse) response);

        long logid = genLogId(uid); //随机数
        //产生logstr
        StringBuilder logsb = new StringBuilder()
                .append(" logid=").append(logid)
                .append(" ip=").append(RequestUtils.getRemoteAddr((HttpServletRequest) request))
                .append(" cmd=").append(((HttpServletRequest) request).getRequestURI());
        String logStr = logsb.toString();
        request.setAttribute(Constant.LOG_STRING, logStr);
        request.setAttribute(Constant.LOG_ID, logid);

        if (!checkResult) {
            try {
                ResponseUtils.buildErrorResult(httpReq, httpRes, "身份校验失败，请重试或重新登录");
            } catch (IOException e) {
                throw e;
            }
            return;
        }

        chain.doFilter(request, response);
    }

    private static long genLogId(long param) {
        long nowTime = System.currentTimeMillis();
        return nowTime & 0x7FFFFFFF | (param >> 8 & 65535L) << 47;
    }

    private boolean checkPpu(String jwt, Long uid, HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.isBlank(jwt)) {
            log.info(" act=PpuCheckFilter.checkPPU ", " jwt 是空的，校验不通过 >>>", " <<<<");
            return false;
        }
        try {
            //token验证  防止泄露，demo 解决
            boolean result =  commondFunction.checkToken(request, uid);
            if (!result){
                return false;
            }
            String newPpuStr = commondFunction.create(uid + "");
            if (StringUtils.isNotEmpty(newPpuStr)) {
                // 刷新cookie
                Cookie cookie = new Cookie("PPU", newPpuStr);
                cookie.setDomain("naixuejiaoyu.com");
                cookie.setPath("/");
                cookie.setMaxAge(86400 * 30);// ppu 种在 cookie 里面 30 天 和 entry 保持一致
                response.addCookie(cookie);
            }
            request.setAttribute(Constant.REQ_UID, uid);
            return true;
        } catch (Exception e) {
            log.error(" act=PpuCheckFilter.checkPPU ", E2s.exception2String(e));
            return false;
        }
    }

    /**
     * 返回不需要校验 PPU 的路径
     */
    public static Set<String> getNotCheckUri(Set<Class<?>> classSet) {
        Set<String> set = Sets.newHashSet();
        // 没有非空判断，因为不可能 controller 为空
        for (Class<?> clazz : classSet) {
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                // 有这个标记的放进去
                RPCMethod methodAnnotation = method.getAnnotation(RPCMethod.class);
                if (!methodAnnotation.needLogin()) {
                    continue;
                }
                // 所有 path 的值都放入映射
                String[] value = method.getAnnotation(RequestMapping.class).value();
                for (String s : value) {
                    set.add(s);
                }
            }
        }
        return set;
    }

    /**
     * 保持这个方法的通用性，方便扩展到别的项目,基础的 Jar 包传过来
     * 入口参数是例子：com.nx
     *
     * @return
     */
    public static Set<Class<?>> getClasses(String packageName) {
        Set<Class<?>> classes = Sets.newHashSet();
        // 把包的名字替换成包的路径
        String packageDirName = packageName.replace(DEFAULT_POINT, File.separatorChar);
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                // 这里只关心文件类型，jar 包里面的类型不管
                if ("file".equals(url.getProtocol())) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 获取所有的class
                    findAndAddClassesInPackageByFile(packageName, filePath, classes);
                } else if ("jar".equals(url.getProtocol())) {
                }
            }
        } catch (Exception e) {
            log.error(" PpuCheckFilter.getClasses ", E2s.exception2String(e));
        }
        return classes;
    }

    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
        File file = new File(packagePath);
        if (!file.exists() || !file.isDirectory()) {
            return;
        }
        // 获取包下所有的子文件夹或者 class 文件
        File[] files = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() || f.getName().endsWith(".class");
            }
        });

        for (File f : files) {
            // 文件夹迭代下面的子文件夹
            if (f.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + DEFAULT_POINT + f.getName(), f.getAbsolutePath(), classes);
            } else {
                // 把文件最后的 .class 去掉
                String className = f.getName().substring(0, f.getName().length() - 6);
                try {
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + DEFAULT_POINT + className));
                } catch (ClassNotFoundException e) {
                    log.error(" PpuCheckFilter.findAndAddClassesInPackageByFile ", E2s.exception2String(e));
                }

            }
        }
    }
}
